﻿using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using WebApi.Core.AuthHelper;
using WebApi.Core.Common.Helper;

namespace WebApi.Core.SetUp
{
    /// <summary>
    /// JWT授权认证
    /// </summary>
    public static class AuthorizationSetup2
    {
        public static void AddAuthorizationSetup2(this IServiceCollection services)
        {
            #region JWT Token Service
            //读取配置文件
            //var audienceConfig = Configuration.GetSection("Audience"); 
            //var symmetricKeyAsBase64 = audienceConfig["Secret"]; 
            //var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); 
            //var signingKey = new SymmetricSecurityKey(keyByteArray); // 令牌验证参数，之前我们都是写在AddJwtBearer里的，这里提出来了

            //var tokenValidationParameters = new TokenValidationParameters
            //{
            //    ValidateIssuerSigningKey = true,//验证发行人的签名密钥
            //    IssuerSigningKey = signingKey,
            //    ValidateIssuer = true,//验证发行人
            //    ValidIssuer = audienceConfig["Issuer"],//发行人
            //    ValidateAudience = true,//验证订阅人
            //    ValidAudience = audienceConfig["Audience"],//订阅人
            //    ValidateLifetime = true,//验证生命周期
            //    ClockSkew = TimeSpan.Zero,//这个是定义的过期的缓存时间
            //    RequireExpirationTime = true,//是否要求过期

            //}; 

            //var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 注意使用RESTful风格的接口会更好，因为只需要写一个Url即可，比如：/api/values 代表了Get Post Put Delete等多个。 // 如果想写死，可以直接在这里写。 //var permission = new List<Permission> { // new Permission {  Url="/api/values", Role="Admin"}, // new Permission {  Url="/api/values", Role="System"}, // new Permission {  Url="/api/claims", Role="Admin"}, // }; // 如果要数据库动态绑定，这里先留个空，后边处理器里动态赋值
            //var permission = new List<Permission>(); // 角色与接口的权限要求参数
            //var permissionRequirement = new PermissionRequirement(
            //    "/api/denied",// 拒绝授权的跳转地址（目前无用）
            //    permission,//这里还记得么，就是我们上边说到的角色地址信息凭据实体类 Permission
            //    ClaimTypes.Role,//基于角色的授权
            //    audienceConfig["Issuer"],//发行人
            //    audienceConfig["Audience"],//订阅人
            //    signingCredentials,//签名凭据
            //    expiration: TimeSpan.FromSeconds(60 * 2)//接口的过期时间，注意这里没有了缓冲时间，你也可以自定义，在上边的TokenValidationParameters的 ClockSkew
            // );

            //services.AddAuthorization(options =>
            //{
            //    options.AddPolicy("Client",
            //        policy => policy.RequireRole("Client").Build());
            //    options.AddPolicy("Admin",
            //        policy => policy.RequireRole("Admin").Build());
            //    options.AddPolicy("SystemOrAdmin",
            //        policy => policy.RequireRole("Admin", "System")); 
            // 自定义基于策略的授权权限
            //    options.AddPolicy("Permission",
            //             policy => policy.Requirements.Add(permissionRequirement));
            //})
            //.AddAuthentication(x =>
            //{
            //    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            //    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            //})
            //.AddJwtBearer(o =>
            //{
            //    o.TokenValidationParameters = tokenValidationParameters;
            //});

            // 依赖注入，将自定义的授权处理器 匹配给官方授权处理器接口，这样当系统处理授权的时候，就会直接访问我们自定义的授权处理器了。
            //services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
            // 将授权必要类注入生命周期内
            //services.AddSingleton(permissionRequirement);
            #endregion



            //////////////////////////////////////////////////////////////


            if (services == null)
                throw new ArgumentNullException(nameof(services));

            //读取配置文件
            var symmetricKeyAsBase64 = AppSettings.app(new string[] { "AppSettings", "JwtSetting", "SecretKey" });
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var Issuer = AppSettings.app(new string[] { "AppSettings", "JwtSetting", "Issuer" });
            var Audience = AppSettings.app(new string[] { "AppSettings", "JwtSetting", "Audience" });

            // 令牌验证参数
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,//是否验证签名,不验证的话可以篡改数据，不安全
                IssuerSigningKey = signingKey,  //解密的密钥
                ValidateIssuer = true,   //是否验证发行人，就是验证载荷中的Iss是否对应ValidIssuer参数
                ValidIssuer = Issuer,//发行人(√)
                ValidateAudience = true,  //是否验证订阅人，就是验证载荷中的Aud是否对应ValidAudience参数
                ValidAudience = Audience,//订阅人(√)
                ValidateLifetime = true,  //是否验证过期时间，过期了就拒绝访问
                ClockSkew = TimeSpan.FromSeconds(30),  //这个是缓冲过期时间，也就是说，即使我们配置了过期时间，这里也要考虑进去，过期时间+缓冲，默认好像是7分钟，你可以直接设置为0
                RequireExpirationTime = true,
            };

            //1【授权】角色策略、这个和上边的异曲同工，好处就是不用在controller中，写多个roles-->eq:SystemOrAdmin 。
            //然后这么写[Authorize(Policy="Admin")]
            //可以对不同的角色建立不同的策略
            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 注意使用RESTful风格的接口会更好，因为只需要写一个Url即可，比如：/api/values 代表了Get Post Put Delete等多个。 
            // 如果想写死，可以直接在这里写。 
            //var permission = new List<Permission>
            //{ 
            //    new Permission {  Url="/api/values", Role="Admin"}, 
            //    new Permission {  Url="/api/values", Role="System"}, 
            //    new Permission {  Url="/api/claims", Role="Admin"}, 
            //}; 
            // 如果要数据库动态绑定，这里先留个空，后边处理器里动态赋值  --->> 见于PermissionHandler2.cs
            
            
            var permission = new List<Permission>(); 
            // 角色与接口的权限要求参数
            var permissionRequirement = new PermissionRequirement(
                "/api/denied",// 拒绝授权的跳转地址（目前无用）
                permission,//这里还记得么，就是我们上边说到的角色地址信息凭据实体类 Permission
                ClaimTypes.Role,//基于角色的授权
                Issuer,//发行人
                Audience,//订阅人
                signingCredentials,//签名凭据
                expiration: TimeSpan.FromSeconds(60 * 2)//接口的过期时间，注意这里没有了缓冲时间，你也可以自定义，在上边的TokenValidationParameters的 ClockSkew
             );
            services.AddAuthorization(options =>
            {
                options.AddPolicy("User", policy => policy.RequireRole("User").Build());
                options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
                // 自定义基于策略的授权权限
                options.AddPolicy("Permission",policy => policy.Requirements.Add(permissionRequirement));
            });



            //2.1【认证】、core自带官方JWT认证
            // 开启Bearer认证
            services.AddAuthentication("Bearer")
            // 添加JwtBearer服务
            .AddJwtBearer(o =>
             {
                 o.TokenValidationParameters = tokenValidationParameters;
                 o.Events = new JwtBearerEvents
                 {
                     OnAuthenticationFailed = context =>
                     {
                         // 如果过期，则把<是否过期>添加到，返回头信息中
                         if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                         {
                             context.Response.Headers.Add("Token-Expired", "true");
                         }
                         return Task.CompletedTask;
                     }
                 };
            });


            // 将授权必要类注入生命周期内
            services.AddSingleton(permissionRequirement);

        }


    }
}
